home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 5 / Apprentice-Release5.iso / Source Code / Add-Ons / After Dark / The Swarm 1.5 / Source / The Swarm.c < prev    next >
Encoding:
Text File  |  1995-12-05  |  36.9 KB  |  1,034 lines  |  [TEXT/R*ch]

  1. /*
  2.  *    The Swarm 1.5 - A Screensaver Module by Leo Breebaart.
  3.  *                      Copyright © 1994-95 Kronto Software.
  4.  *
  5.  *
  6.  *      For non-technical information about this module and for the credits,
  7.  *    see the README file.
  8.  *       
  9.  *       This module displays a variable number of line-segments (the ‘Bees’), which 
  10.  *       chase another line segment (the ‘Queen’) across the screen.
  11.  *       
  12.  *       I have commented the code in a way that experienced programmers will find
  13.  *       overkill, but it was done in the hope that beginning programmers
  14.  *       will find “The Swarm” a useful starting point for writing their own 
  15.  *       screensaver modules.
  16.  *       
  17.  *       For general information about how to write an After Dark module, 
  18.  *       see the After Dark Programmer’s Manual, but make sure you have the most recent
  19.  *       version (released with After Dark 3.0 and later). The comments in this
  20.  *    module do assume you are at least *aware* of the basic After Dark mechanisms.
  21.  *
  22.  *      Here we go...
  23.  */
  24.  
  25.     // Include file for After Dark type definitions.
  26. #include "GraphicsModule_Types.h"
  27.  
  28.     // Prototypes for After Dark functions.
  29. #include "ModuleFunctions.h"
  30.  
  31.     // Utility functions.
  32. #include "HandyStuff.h"
  33.  
  34.     // Data structures.
  35. #include "The Swarm.h"
  36.  
  37.     // Interface to Jonas Englund's CLUT fade library for nice fade-in/fade-out effects.
  38. #include "Fade.h"
  39.  
  40.  
  41. #define kClutID 132                    // Resource ID of color lookup table (clut)
  42.  
  43.     // Constants
  44. #define kMaxBees 100                // Maximum number of Bees.
  45. #define kQueenMaxVelocity 12        // Maximum Queen velocity (in pixels per frame).
  46. #define kQueenMaxAcceleration 5        // Maximum Queen acceleration.
  47. #define kBeeMaxVelocity  11            // Maximum Bee velocity.
  48. #define kBeeMaxAcceleration  3        // Maximum Bee acceleration.
  49. #define kBorderWidth  50            // Queen won't go any nearer than this many pixels to the
  50.                                     // edge of the screen and the demo rectangle.
  51.                             
  52.     // Here we define some handy macros for accessing the 
  53.     // Swarm's position and velocity values. These macros work under the
  54.     // assumption that you have a valid handle variable named 'swarm', pointing
  55.     // to a storage struct as defined in "The Swarm.h".
  56.  
  57.     // Position of Bee b at time t (where t is either 0 or 1).
  58. #define BX(t,b)  (*((*(swarm->beeX[t]))+(b)))
  59. #define BY(t,b)  (*((*(swarm->beeY[t]))+(b)))
  60.  
  61.     // Current velocity of Bee b.
  62. #define BXV(b)    (*((*swarm->beeVelocityX)+(b)))
  63. #define BYV(b)    (*((*swarm->beeVelocityY)+(b)))
  64.  
  65.     // Position of the Queen at time t (where t is either 0 or 1).
  66. #define QX(t)  (swarm->queenX[t])
  67. #define QY(t)  (swarm->queenY[t])
  68.  
  69.     // Current velocity of the Queen.
  70. #define QXV    (swarm->queenVelocityX)
  71. #define QYV    (swarm->queenVelocityY)
  72.  
  73.     // Some more handy macros.
  74. #define RAND(v) (RangedRdm(-(v)/2, (v)/2))        // Random integer around 0.  
  75. #define abs(x) (((x) > 0) ? (x) : -(x))            // Standard macro for 'abs'.
  76.  
  77. #define setmin(x, min) if (x < min) min = x;    // Keep track of a minimum value.
  78. #define setmax(x, max) if (x > max) max = x;    // Keep track of a maxmimum value.
  79.  
  80.     // After Dark's way of showing an error to the user.
  81.     // '##' is an ANSI-ism meaning meta-concatenation.
  82. #define ErrorMsg(m) BlockMove(CtoPstr(m), params->errorMessage, 1 + m##[0]);
  83.  
  84.     // In order to avoid unexpected unpleasant surprises...
  85. #define SafeDisposHandle(h) if ((Handle)(h)) DisposeHandle((Handle)(h))
  86.  
  87.     // QuickDraw color constants used throughout the program.
  88. RGBColor gWhite = { 0xFFFF, 0xFFFF, 0xFFFF };
  89. RGBColor gBlack = { 0, 0, 0 };
  90. RGBColor gGold  = { 0xFFFF, 0x9C9C, 0x0808 };
  91. RGBColor gBlue    = {    0x0808, 0x0000, 0x2929 };
  92. RGBColor gRed    = { 0xBDBD, 0x0000, 0x0000 };
  93.  
  94.     // Now we come to the fun part: the actual implementations of the functions.
  95.     // First: DoInitialize, which allocates our swarm data structure as defined
  96.     // above, initializes the variables in that struct, and does a gazillion checks
  97.     // for possible problems.
  98.  
  99. OSErr
  100. DoInitialize (Handle *storage, GMParamBlockPtr params)
  101. {
  102.     register TSwarmDataPtr swarm;    // The swarm, obviously.
  103.     register short b;                // Index for Bee loops.
  104.         
  105.         // Local variables in order to avoid having to write
  106.         // 'swarm->...' all the time. Could have used macro's here as
  107.         // well, I suppose.
  108.     RGBColor whiteRGB, blackRGB, queenRGB, beeRGB;
  109.     short nBees;
  110.     long delay;
  111.     
  112.         // If After Dark is in demo-mode, the demoRect can be found in the
  113.         // params argument to this function, but we need to make temporary
  114.         // changes to it, so we use a local copy.
  115.     Rect borderedDemoRect;    
  116.  
  117.          // Our offscreen graphics world needs Color QuickDraw.
  118.     if (!params->colorQDAvail)
  119.     {
  120.         DisposHandle(*storage);
  121.         ErrorMsg("The Swarm:  Sorry, I need Color QuickDraw to run!");
  122.         return ModuleError;
  123.     }
  124.     
  125.         // Allocate 'master' handle to the storage struct.
  126.     if ((*storage = BestNewHandle(sizeof(TSwarmData))) == NULL)
  127.     {     
  128.         ErrorMsg("The Swarm:  Couldn't allocate enough memory!");
  129.         return ModuleError;
  130.     }
  131.  
  132.         // Lock down the storage so we can safely refer to it by pointer. 
  133.     HLockHi(*storage);
  134.     swarm = (TSwarmDataPtr) **storage;
  135.         
  136.         // Initialize the random number generator.
  137.     params->qdGlobalsCopy->qdRandSeed = TickCount();
  138.  
  139.         // Set up and initialize the colors we'll be using.
  140.                     
  141.         // Absolute black and white.
  142.     whiteRGB.red = whiteRGB.green = whiteRGB.blue = 0xFFFF;
  143.     blackRGB.red = blackRGB.green = blackRGB.blue = 0;
  144.     
  145.         // A golden color for the Bees, a bright red for the Queen.
  146.     beeRGB.red = 0xFFFF; beeRGB.green = 0x9C9C; beeRGB.blue = 0x0808;
  147.     queenRGB.red = 0xBDBD; queenRGB.green = 0; queenRGB.blue = 0;
  148.     
  149.         // The Queen's red color gets mapped to black by the Mac
  150.         // if the screen is less then 8-bit. We don't want that...
  151.     if (params->monitors->monitorList[0].curDepth < 4)
  152.         queenRGB = whiteRGB;
  153.  
  154.         // This is a very crude and stupid way to try and detect if the main screen
  155.         // is in grayscale mode. If so, I make the queen white-on-black,
  156.         // since anything else is guaranteed to look bad.
  157.         // In the next version, this will all be solved much cleaner.
  158.     if (!(params->systemConfig & (1L << 3)))
  159.         queenRGB = whiteRGB;
  160.  
  161.         // Initialize the struct variables from our local convenience ones.
  162.     swarm->whiteRGB = whiteRGB;
  163.     swarm->blackRGB = blackRGB;
  164.     swarm->queenRGB = queenRGB;
  165.     swarm->beeRGB   = beeRGB;
  166.     swarm->backRGB  = blackRGB;
  167.     
  168.         // We get our number of bees from a slide control in the
  169.         // After Dark control panel interface. This number can be zero!
  170.     swarm->nBees = nBees = params->controlValues[0];
  171.     
  172.         // We get the animation speed from a second slide control.
  173.         // This is primarily intended for Macs which are *too fast*,
  174.         // which is why we express speed in terms of the delay between frames.
  175.         // This delay will be either 0, 2, 4, 6, 8 or 10 system ticks long.
  176.     swarm->delay = delay = (long)10 - (2*params->controlValues[1] / 20);
  177.     
  178.         // We get the color changing speed from a third slide control.
  179.         // There is no neat functional mapping from the slider value to
  180.         // this speed value, hence the cascading if-statements.
  181.         // The speed values are expressed in animation frames, i.e.
  182.         // a switchColor of 25 means that the color will changes every
  183.         // 25 animation steps, and a value of 1 means the color changes
  184.         // at every step. The value of 0 is a special, and signifies
  185.         // no color change at all. Instead the bees will have the golden
  186.         // color used in version 1.0 of this module.
  187.     if (params->controlValues[2] == 0)
  188.         swarm->colStepCnt = swarm->switchColor = 0;
  189.     else if (params->controlValues[2] > 0 && params->controlValues[2] < 25)
  190.         swarm->colStepCnt = swarm->switchColor = 25;
  191.     else if (params->controlValues[2] >= 25 && params->controlValues[2] < 50)
  192.         swarm->colStepCnt = swarm->switchColor = 10;
  193.     else if (params->controlValues[2] >= 50 && params->controlValues[2] < 75)
  194.         swarm->colStepCnt = swarm->switchColor = 5;
  195.     else if (params->controlValues[2] >= 75 && params->controlValues[2] < 100)
  196.         swarm->colStepCnt = swarm->switchColor = 3;
  197.     else if (params->controlValues[2] == 100)
  198.         swarm->colStepCnt = swarm->switchColor = 1;
  199.     
  200.         // A random start color, which I don't want to be red (because that
  201.         // doesn't look neat enough), hence the 30-220 range of clut indexes.    
  202.     swarm->colIndex = RangedRdm(30, 220);
  203.     
  204.         // Our index can go up or down the clut table (with wrap around at the
  205.         // ends.
  206.     swarm->colDirection = (RandomBool() ? 1 : -1);
  207.     
  208.         // The clut fading routines only work or 8-bit clut devices.
  209.     swarm->doFade = (params->monitors->monitorList[0].curDepth == 8);
  210.     
  211.         // Sanity checks. These things should never occur.
  212.     if (nBees < 0 || nBees > 100)
  213.     {
  214.         DoClose(*storage, (RgnHandle) nil, (GMParamBlockPtr) nil);
  215.         ErrorMsg("The Swarm:  Internal Error — insane number of bees!");
  216.         return ModuleError;
  217.     }
  218.     
  219.     if (delay < 0 || delay > 10)
  220.     {
  221.         DoClose(*storage, (RgnHandle) nil, (GMParamBlockPtr) nil);
  222.         ErrorMsg("The Swarm:  Internal Error — insane bee speed value!");
  223.         return ModuleError;
  224.     }
  225.     
  226.     if (swarm->switchColor < 0 || swarm->switchColor > 100)
  227.     {
  228.         DoClose(*storage, (RgnHandle) nil, (GMParamBlockPtr) nil);
  229.         ErrorMsg("The Swarm:  Internal Error — insane color speed value!");
  230.         return ModuleError;
  231.     }
  232.     
  233.         // See DoDrawFrame for more info on how this variable is used to implement
  234.         // the delay.
  235.     swarm->startDrawing = LMGetTicks();
  236.  
  237.         // We need to know if we are in demo Mode in the DoDrawFrame function,
  238.         // but we want to avoid having to call the EmptyRect function every time,
  239.         // so we store it in a Boolean here.
  240.     swarm->demoMode = !EmptyRect((¶ms->demoRect));
  241.         
  242.         // Allocate handles, and afterwards (a) check if the allocation
  243.         // succeeded, and (b) move the handles to high memory and lock them.
  244.         // I could have done this with static arrays (beeX[2][kMaxBees]),
  245.         // I suppose, but I wanted to learn how to work with handles.
  246.         // The speed difference in access is, to my surprise, negligible,
  247.         // So I see no reason to change it back.
  248.         //
  249.         // Notice that we allocate enough memory for the maximum number
  250.         // of Bees in advance. This is because in Demo Mode we want to
  251.         // be able to let the user dynamically change nBees, which is
  252.         // the *current* number of Bees, if you'll remember. In order to
  253.         // do that neatly, we want to have the storage ready and initialized
  254.         // beforehand.
  255.         
  256.     swarm->beeX[0] = (ShortHandle) BestNewHandle(kMaxBees*sizeof(short));
  257.     swarm->beeX[1] = (ShortHandle) BestNewHandle(kMaxBees*sizeof(short));
  258.     swarm->beeY[0] = (ShortHandle) BestNewHandle(kMaxBees*sizeof(short));
  259.     swarm->beeY[1] = (ShortHandle) BestNewHandle(kMaxBees*sizeof(short));
  260.  
  261.     swarm->beeVelocityX = (ShortHandle) BestNewHandle(kMaxBees*sizeof(short));
  262.     swarm->beeVelocityY = (ShortHandle) BestNewHandle(kMaxBees*sizeof(short));
  263.     
  264.     if (!(swarm->beeX[0] && swarm->beeX[1] && swarm->beeY[0] && swarm->beeY[1]
  265.           && swarm->beeVelocityX && swarm->beeVelocityY))
  266.     {
  267.         DoClose(*storage, (RgnHandle) nil, (GMParamBlockPtr) nil);
  268.         ErrorMsg("The Swarm:  Couldn't allocate enough internal memory!");
  269.         return ModuleError;
  270.     }
  271.     
  272.         // Lok all the handles so we won't have to worry about things
  273.         // being moved out from under our feet.
  274.     HLockHi((Handle) swarm->beeX[0]);
  275.     HLockHi((Handle) swarm->beeX[1]);
  276.     HLockHi((Handle) swarm->beeY[0]);
  277.     HLockHi((Handle) swarm->beeY[1]);
  278.     
  279.     HLockHi((Handle) swarm->beeVelocityX); 
  280.     HLockHi((Handle) swarm->beeVelocityY);
  281.  
  282.         // Swarm animation uses *only* the main monitor (...[0]).
  283.     swarm->monitorRect = params->monitors->monitorList[0].bounds;
  284.     swarm->winW   = swarm->monitorRect.right - swarm->monitorRect.left;     
  285.     swarm->winH   = swarm->monitorRect.bottom - swarm->monitorRect.top;
  286.     
  287.         // For different monitor sizes, we want the relation between
  288.         // border and winW to be the same as that between kBorderWidth and 640
  289.         // (my 13" screen). The cast to 'long' is because the multiplication
  290.         // will overflow a 2-byte short for large monitors. You have no *idea*
  291.         // how long it took me to discover this one (primarily because I don't
  292.         // *have* a large monitor).
  293.     swarm->border = ((long) kBorderWidth * swarm->winW) / 640;
  294.     
  295.         // Initialize variables with earlier defined constants.
  296.     swarm->maxQueenVelocity     = kQueenMaxVelocity;
  297.     swarm->maxBeeVelocity          = kBeeMaxVelocity;
  298.     swarm->maxQueenAcceleration = kQueenMaxAcceleration;
  299.     swarm->maxBeeAcceleration      = kBeeMaxAcceleration;
  300.     
  301.         // Make both bounding rectangles intially empty.
  302.     SetRect(&swarm->oldRect, 0, 0, 0, 0);
  303.     SetRect(&swarm->swarmRect, 0, 0, 0, 0);
  304.         
  305.         // Initial Queen position. The checks are sanity checks for the
  306.         // RangedRdm function which I'm leaving in because I don't
  307.         // trust RangedRdm at all.
  308.     QX(0) = swarm->monitorRect.left + RangedRdm(swarm->border, swarm->winW - swarm->border);
  309.     if (QX(0) < 10)
  310.     {
  311.         DoClose(*storage, (RgnHandle) nil, (GMParamBlockPtr) nil);
  312.         ErrorMsg("The Swarm:  Internal Error — Initial Queen position: X coordinate is smaller than 10!");
  313.         return ModuleError;
  314.     }
  315.     
  316.     QY(0) = swarm->monitorRect.top + RangedRdm(swarm->border, swarm->winH - swarm->border);
  317.     if (QY(0) < 10)
  318.     {
  319.         DoClose(*storage, (RgnHandle) nil, (GMParamBlockPtr) nil);
  320.         ErrorMsg("The Swarm:  Internal Error — Initial Queen position: Y coordinate is smaller than 10!");
  321.         return ModuleError;
  322.     }
  323.  
  324.         // If in demo mode, then make sure that the
  325.         // initial Queen position lies at least 'border' pixels *outside* the demoRect.
  326.     if (swarm->demoMode)
  327.     {
  328.         borderedDemoRect = params->demoRect;
  329.         InsetRect(&borderedDemoRect, -swarm->border, -swarm->border);
  330.  
  331.         while (XYInRect(&borderedDemoRect, QX(0), QY(0)))
  332.         {
  333.             QX(0) = swarm->monitorRect.left + RangedRdm(swarm->border, swarm->winW - swarm->border);
  334.             QY(0) = swarm->monitorRect.top + RangedRdm(swarm->border, swarm->winH - swarm->border);
  335.         }
  336.     }
  337.     if (QX(0) < 10)
  338.     {
  339.         DoClose(*storage, (RgnHandle) nil, (GMParamBlockPtr) nil);
  340.         ErrorMsg("The Swarm:  Internal Error — Initial Queen position: X coordinate is smaller than 10, after the demo check!");
  341.         return ModuleError;
  342.     }
  343.  
  344.     if (QY(0) < 10)
  345.     {
  346.         DoClose(*storage, (RgnHandle) nil, (GMParamBlockPtr) nil);
  347.         ErrorMsg("The Swarm:  Internal Error — Initial Queen position: Y coordinate is smaller then 10, after the demo check!");
  348.         return ModuleError;
  349.     }
  350.  
  351.         // For the first frame, our line segment is just a point.
  352.     QX(1) = QX(0);
  353.     QY(1) = QY(0);
  354.     QXV = QYV = 0;
  355.     
  356.         // Ditto for the Bees, although (a) all Bees start from the same
  357.         // physical position as the Queen, and (b) all Bees have a different
  358.         // initial velocity. This gives a nice 'fountaining' effect on startup
  359.         // of the animation.
  360.     for (b = 0; b < kMaxBees; b++)
  361.     {
  362.         BX(0,b) = QX(0);
  363.         BX(1,b) = BX(0,b);
  364.         BY(0,b) = QY(0);
  365.         BY(1,b) = BY(0,b);
  366.  
  367.         BXV(b) = RAND(7);
  368.         BYV(b) = RAND(7);
  369.     }
  370.         
  371.         // Now allocate an offscreen graphics world, where we can do
  372.         // fast drawing without disturbing the real screen.
  373.         // I recommend looking up this call in Inside Macintosh or Think Reference.
  374.     if (NewGWorld(&(swarm->gMyOffG), 0, &(swarm->monitorRect), nil, nil, noNewDevice+useTempMem) != noErr)      
  375.     {     
  376.         DoClose(*storage, (RgnHandle) nil, (GMParamBlockPtr) nil);
  377.         ErrorMsg("The Swarm:  Not enough memory for offscreen graphics world!");
  378.         return ModuleError;            
  379.     }
  380.         
  381.     return noErr;
  382. }
  383.  
  384.  
  385.     // Next, the DoBlank function. This function performs two tasks: it blacks
  386.     // out the real screen (on all monitors, not just the main monitor we use for the
  387.     // animation), and it also blacks out the offworld screen.
  388.  
  389. OSErr
  390. DoBlank (Handle storage, RgnHandle blankRgn, GMParamBlockPtr params)
  391. {
  392.     register TSwarmDataPtr swarm;
  393.             
  394.     GWorldPtr        currPort;    // The 'real' screen consists of two components
  395.     GDHandle        currDev;    // which we'll save in these variables.
  396.  
  397.         // It's only safe to do this because we know the handle is still locked!
  398.     swarm = (TSwarmDataPtr) *storage;
  399.     
  400.         // Save the 'real' screen.
  401.     GetGWorld(&currPort,&currDev);
  402.     
  403.         // Switch to the offscreen world.
  404.     SetGWorld((swarm->gMyOffG), nil);
  405.     
  406.         // Set the backgroundcolor, and erase the whole world.
  407.        RGBBackColor(&swarm->backRGB);
  408.     EraseRgn(blankRgn);
  409.     
  410.         // Switch back to the 'real' screen.
  411.     SetGWorld (currPort, currDev);
  412.     
  413.         // This is not redundant! We are in a different world now...
  414.        RGBBackColor(&swarm->backRGB);
  415.  
  416.         // Do a nice fade-out/fade-in if the user specified
  417.         // that check box in the Control Panel, and if the monitor
  418.         // is capable of doing so.
  419.         // The routines I use are *only* fit for 8-bit CLUT displays, so
  420.         // don't ever try changing this!
  421.     if (swarm->doFade && params->monitors->monitorList[0].curDepth == 8)
  422.     {
  423.         fade_screen(96,true);
  424.         EraseRgn(blankRgn);
  425.            fade_screen(64,false);
  426.        }
  427.        else
  428.         EraseRgn(blankRgn);
  429.        
  430.            // If we'll be doing color animation then install the new color table.
  431.     if (swarm->doFade && swarm->switchColor > 0)
  432.            install_clut(kClutID);
  433.        
  434.     return noErr;
  435. }
  436.  
  437.  
  438.     // Finally we come to the meat of our module: the DoDrawFrame function.
  439.     // Although there is a lot of code here, what actually happens is quite simple.
  440.     // First, all the position and velocity variables are updated and checked for
  441.     // bouncing etc. Then, we *erase* (in the offscreen world) the 'old' swarm
  442.     // by filling the swarm's bounding rectangle (which will vary from frame to frame!)
  443.     // with the background color. Then, we draw the 'new' swarm. Finally, we use
  444.     // the famous 'CopyBits' function to move the changed areas of the offscreen
  445.     // world to the real screen world. And that's all.
  446.     
  447.     // One more comment: a lot of the array accesses could have been optimized
  448.     // to make the code run faster. Local variables could have been used to avoid
  449.     // the indirect accesses caused by all those global handles and arrays.
  450.     // The BlockMove function could have been called to update entire arrays in
  451.     // one sweep.
  452.     // However, all those accesses taken together still take up only a negligable fraction
  453.     // of this function's total execution time, when compared to the cost of
  454.     // doing the graphics. CopyBits is an expensive function, and for large numbers
  455.     // of Bees the drawing of the line segments takes even more time.
  456.     // This is why I have decided to refrain from optimizing. It doesn't matter very
  457.     // much speedwise, and such optimizations would only obscure the underlying algorithm.
  458.     
  459. OSErr
  460. DoDrawFrame (Handle storage, GMParamBlockPtr params)
  461. {        
  462.     register TSwarmDataPtr swarm;
  463.     
  464.         // Again, lots of local variables in order to avoid
  465.         // too much 'swarm->...' stuff.
  466.     RGBColor beeRGB, queenRGB, whiteRGB, backRGB;
  467.     Rect monitorRect, *swarmRect, *oldRect;
  468.     short winW, winH, nBees, border;
  469.     long delay;
  470.     
  471.         // Variables used for calculating the swarm's bounding rectangle.
  472.     short xMin=10000, xMax=-10000, yMin=10000, yMax=-10000;
  473.  
  474.     GWorldPtr currPort;        // The 'real' screen consists of two components.
  475.     GDHandle  currDev;        // which we'll save in these variables.
  476.     
  477.         // The actual pixel maps of offscreen and real screen, respectively.
  478.     PixMapHandle offBase, realBase;
  479.  
  480.     Rect unionRect;
  481.     
  482.         // Distances from a Bee to the Queen.
  483.     short dx, dy, distance;            
  484.  
  485.         // Bee counter for loops.
  486.     register short b;
  487.  
  488.         // storage was already locked in DoInitialize, so this is safe.
  489.     swarm = (TSwarmDataPtr) *storage;
  490.     
  491.     nBees = swarm->nBees;
  492.     delay = swarm->delay;
  493.  
  494.         // If we are in Demo mode, reread the nBees and delay values -- the user may have
  495.         // changed them dynamically.
  496.         // At present it is not possible to change the color speed value in Demo
  497.         // mode. Not because it is so difficult to do, but it means lots of stupid
  498.         // extra code, and I just don't feel like bothering, frankly.
  499.     if (swarm->demoMode)
  500.     {
  501.         if (nBees != params->controlValues[0])
  502.             nBees = swarm->nBees = params->controlValues[0];
  503.  
  504.         if (delay != params->controlValues[1]) 
  505.             delay = swarm->delay = (long)10 - (2*params->controlValues[1] / 20);
  506.     }
  507.  
  508.         // The next thing to do is to check for delay. This can be done
  509.         // naively by simply using the Delay system function (with 'swarm->delay' as
  510.         // parameter), but that would mean that we would simply spend those
  511.         // ticks *doing nothing* -- and not allow anything else to do anything
  512.         // either. That's why we do it differently: if the delay has not passed yet,
  513.         // we simply exit from this function at once, and wait until we are called again.
  514.         // That way, almost all the delay time is given to the After Dark parent
  515.         // process which can then presumeably use it to check for system activity,
  516.         // or give it to background processes, etc.
  517.         
  518.     if (delay != 0)
  519.         if (TickCount() < swarm->startDrawing)
  520.             return noErr;
  521.         else
  522.             swarm->startDrawing = TickCount() + delay;
  523.     
  524.         // Initialize the other local variables from their swarm counterparts.
  525.     monitorRect = swarm->monitorRect;
  526.     winW   = swarm->winW;
  527.     winH   = swarm->winH;
  528.     border = swarm->border;
  529.     
  530.     backRGB  = swarm->backRGB;
  531.     whiteRGB = swarm->whiteRGB;
  532.     queenRGB = swarm->queenRGB;
  533.     beeRGB   = swarm->beeRGB;
  534.     
  535.     swarmRect = &swarm->swarmRect;
  536.     oldRect   = &swarm->oldRect;
  537.     
  538.         // Age the swarm bouding rectangle.
  539.     *oldRect = *swarmRect;
  540.     
  541.         // First, we do the Queen Stuff:
  542.  
  543.         // Age the position arrays. 
  544.     QX(1) = QX(0);
  545.     QY(1) = QY(0);
  546.         
  547.         // Accelerate.
  548.     QXV += RAND(swarm->maxQueenAcceleration);
  549.     QYV += RAND(swarm->maxQueenAcceleration);
  550.  
  551.         // Speed limit checks.
  552.     if (QXV > swarm->maxQueenVelocity)
  553.         QXV = swarm->maxQueenVelocity;
  554.     else 
  555.         if (QXV < -swarm->maxQueenVelocity)
  556.             QXV = -swarm->maxQueenVelocity;
  557.     if (QYV > swarm->maxQueenVelocity)
  558.         QYV = swarm->maxQueenVelocity;
  559.     else
  560.         if (QYV < -swarm->maxQueenVelocity)
  561.             QYV = -swarm->maxQueenVelocity;
  562.  
  563.         // Fill new 'current' positions.
  564.     QX(0) = QX(1) + QXV;
  565.     QY(0) = QY(1) + QYV;
  566.  
  567.         // Bounce Checks.
  568.     if ((QX(0) < monitorRect.left + border) || (QX(0) > monitorRect.left + winW - border - 1))
  569.     {
  570.             // These two statements (and all similar ones further on)
  571.             // cause a swarm element to 'bounce off' according to a
  572.             // "angle of entry is angle of exit" rule. It looks much more
  573.             // cryptic than it really is. Trust me.
  574.         QXV = -QXV;
  575.         QX(0) += QXV << 1;
  576.     }
  577.     if ((QY(0) < monitorRect.top + border) || (QY(0) > monitorRect.top + winH - border - 1))
  578.     {
  579.         QYV = -QYV;
  580.         QY(0) += QYV << 1;
  581.     }
  582.     
  583.          // If we are in demo Mode, we want the Queen to 'bounce' off
  584.          // the demoRect as well. This takes some hairy additional testing :-)
  585.     
  586.         // Without these macro's 'hairy' would become 'bloody incomprehensible'.
  587.  #define DL (params->demoRect.left - border/2)
  588.  #define DR (params->demoRect.right + border/2)
  589.  #define DT (params->demoRect.top - border/2)
  590.  #define DB (params->demoRect.bottom + border/2)
  591.  
  592.          // The actual tests.
  593.     if (!EmptyRect(¶ms->demoRect))
  594.     {
  595.          if ((QX(0) < DR) && (QX(0) > DL) && (QY(0) < DB) && (QY(0) > DT))
  596.         {
  597.             if ((QX(1) <= DL) || (QX(1) >= DR))
  598.             {
  599.                 QXV = -QXV;
  600.                  QX(0) += QXV << 1;
  601.              }
  602.              if ((QY(1) <= DT) || (QY(1) >= DB))
  603.              {
  604.                 QYV = -QYV;
  605.                  QY(0) += QYV << 1;
  606.             }
  607.         }
  608.        }
  609.         
  610.         // Keep track of the minimal bouding rect of the swarm so far.
  611.     setmin(QX(0), xMin);
  612.     setmin(QY(0), yMin);
  613.     setmax(QX(0), xMax);
  614.     setmax(QY(0), yMax);
  615.  
  616.     setmin(QX(1), xMin);
  617.     setmin(QY(1), yMin);
  618.     setmax(QX(1), xMax);
  619.     setmax(QY(1), yMax);
  620.     
  621.    
  622.            // Now we get to the Bee stuff, which is basically
  623.            // the same, except that (a) Bees do *not* bounce off walls or
  624.            // demoRects, and (b) Bees will try to 'follow' the Queen.
  625.             
  626.         // First, don't ever let things settle down. 
  627.     if (nBees > 0)          // Avoid later division by 0!
  628.     {
  629.         BXV(RangedRdm(0, nBees)) += RAND(3);
  630.         BYV(RangedRdm(0, nBees)) += RAND(3);
  631.     }
  632.     
  633.     for (b = 0; b < nBees; b++)
  634.     {
  635.             // Age the arrays. 
  636.         BX(1, b) = BX(0, b);
  637.         BY(1, b) = BY(0, b);
  638.         
  639.             // Accelerate.
  640.         dx = QX(1) - BX(1, b);
  641.         dy = QY(1) - BY(1, b);
  642.             
  643.             // This calculation of the true distance from the dx/dy values
  644.             // is an approximation that allows us to keep everything in 
  645.             // integer math. Otherwise we'd have do square root operations...
  646.         distance = abs(dx) + abs(dy); 
  647.         if (distance == 0)
  648.             distance = 1;
  649.         BXV(b) += (dx * swarm->maxBeeAcceleration) / distance;         
  650.         BYV(b) += (dy * swarm->maxBeeAcceleration) / distance;
  651.  
  652.             // Speed limit checks.
  653.         if (BXV(b) > swarm->maxBeeVelocity)
  654.             BXV(b) = swarm->maxBeeVelocity;
  655.         else 
  656.             if (BXV(b) < -swarm->maxBeeVelocity)
  657.                 BXV(b) = -swarm->maxBeeVelocity;
  658.         if (BYV(b) > swarm->maxBeeVelocity)
  659.             BYV(b) = swarm->maxBeeVelocity;
  660.         else
  661.             if (BYV(b) < -swarm->maxBeeVelocity)
  662.                 BYV(b) = -swarm->maxBeeVelocity;
  663.  
  664.             // Fill new 'current' positions.
  665.         BX(0, b) = BX(1, b) + BXV(b);
  666.         BY(0, b) = BY(1, b) + BYV(b);
  667.         
  668.             // Keep track of the minimal bouding rect of the swarm so far.
  669.         setmin(BX(0,b), xMin);
  670.         setmax(BX(0,b), xMax);
  671.         setmin(BX(1,b), xMin);
  672.         setmax(BX(1,b), xMax);
  673.  
  674.         setmin(BY(0,b), yMin);
  675.         setmax(BY(0,b), yMax);
  676.         setmin(BY(1,b), yMin);
  677.         setmax(BY(1,b), yMax);   
  678.     }       
  679.  
  680.         // swarmRect will now be set to the minimal bounding rectangle we've been maintaining,
  681.         // which we want to 'clip' by intersecting it with the screen rectangle,
  682.         // and which we finally need to combine with the *old* bounding rectangle,
  683.         // to give as a result the minimal bounding rectangle of the entire screen
  684.         // area that has been affected by this step of the animation.
  685.         
  686.     SetRect(swarmRect, xMin-1, yMin-1, xMax+1, yMax+1);
  687.     SectRect(&monitorRect, swarmRect, swarmRect);
  688.     UnionRect(oldRect, swarmRect, &unionRect);
  689.     
  690.         // Save the real screen world.
  691.     GetGWorld(&currPort, &currDev);
  692.     
  693.         // Switch to the offscreen world.
  694.     SetGWorld (swarm->gMyOffG, nil);
  695.         
  696.         // Erase the old swarm bounding rectangle.
  697.     RGBBackColor(&swarm->backRGB);
  698.     EraseRect(oldRect);
  699.     
  700.         // Draw the new swarm, first Queen, then Bees.
  701.     RGBForeColor(&queenRGB);
  702.     LineFromTo(QX(0),QY(0), QX(1),QY(1));
  703.  
  704.         // Handle the color 'animation'.
  705.         
  706.         // Are we (capable of) animating?
  707.     if (swarm->doFade && swarm->switchColor > 0)
  708.     {        
  709.             // Should we be doing a color switch at this point in time?
  710.         if (swarm->colStepCnt == swarm->switchColor)
  711.         {                
  712.                 // Update bee color.
  713.             Index2Color(swarm->colIndex, &swarm->beeRGB);
  714.                                     
  715.                 // Once in a while, change the direction in
  716.                 // which we're traversing the color lookup table.
  717.             if (RangedRdm(0, 300) == 0)
  718.                 swarm->colDirection = -swarm->colDirection;
  719.  
  720.                 // Update counter (with wrap around).
  721.                 // For reasons I do not currently understand the color
  722.                 // animation will not work correctly if I use the clut
  723.                 // indexes 0-4 and 251-254. If you do know why this is
  724.                 // so, please drop me a line!
  725.             swarm->colIndex += swarm->colDirection;
  726.             if (swarm->colIndex > 250)
  727.                 swarm->colIndex = 5;
  728.             if (swarm->colIndex < 5)
  729.                 swarm->colIndex = 250;
  730.                 
  731.             swarm->colStepCnt = 0;
  732.         }
  733.         else
  734.             swarm->colStepCnt++;
  735.     }
  736.     
  737.         
  738.     RGBForeColor(&swarm->beeRGB);
  739.  
  740.         // Draw the Bees.
  741.     for (b = 0; b < nBees; b++)
  742.         LineFromTo(BX(0, b),BY(0, b), BX(1, b),BY(1, b));
  743.  
  744.         // Switch back to the real screen.    
  745.     SetGWorld(currPort, currDev);               
  746.        
  747.         // Retrieve actual pixel maps for both offscreen and real screen.
  748.     realBase = GetGWorldPixMap((GWorldPtr) currPort);
  749.     offBase  = GetGWorldPixMap(swarm->gMyOffG);
  750.  
  751.            // These next two calls appear to make no sense, but
  752.            // are absolutely necessary for CopyBits to function
  753.            // correctly. See the Apple TechNote on this subject (or Inside Macintosh)
  754.     RGBBackColor(&whiteRGB);
  755.     RGBForeColor(&swarm->blackRGB);            
  756.  
  757.        // Blit the changed area from offscreen to realscreen.
  758.     CopyBits((BitMap *) (*offBase), (BitMap *) (*realBase), 
  759.               &unionRect, &unionRect,
  760.               srcCopy, nil);
  761.                    
  762.     return noErr;
  763. }
  764.  
  765.  
  766.     // Well, the hard part is now over -- almost. "The Swarm" also features a
  767.     // funky About Box, which has a miniature version of the swarm animation
  768.     // going on inside of it. Creating that animation was simpler than I feared it would
  769.     // be: for the most part I just call the previous functions, i.e. DoInitialize,
  770.     // DoBlank, and DoDrawFrame -- and that's it. The tricky part lies in getting
  771.     // some correct variables in place, and setting up the right graphics port.
  772.     //
  773.     // One thing you should realize about DoAboutBox: when After Dark calls this
  774.     // function, it will already have set the currPort and the blankRgn to the help rectangle. 
  775.     // So you are at this point no longer drawing to the entire screen.
  776.     //
  777.     // Final note: if you want to use a DoAboutBox function in your own module, realize  
  778.     // that (a) you'll need to tell After Dark you want to 'take over' (see the Cals resource in
  779.     // the Programmer's Manual), and (b) 'storage' is *not* initialized in DoHelp,
  780.     // because After Dark calls DoAboutBox without calling DoInitialize first, in contrast
  781.     // to e.g. DoDrawFrame. Unfortunately, a lot of code shows example 'DoAbout' or
  782.     // 'DoAboutBox' functions with the 'storage' handle as a parameter. But this parameter
  783.     // will *not* be intialized, and if you use it, you will crash horribly. So in my
  784.     // my code, I don't even include it.
  785.  
  786. OSErr
  787. DoAboutBox (GMParamBlockPtr params)
  788. {
  789.     // If you've read and understood DoInitialize and DoDrawFrame, I think you'll
  790.     // have enough knowledge so that I won't have to annotate every single variable
  791.     // here, right?
  792.  
  793.     TSwarmData **miniSwarm;
  794.        
  795.     long dummy;
  796.     GrafPtr helpGraf;
  797.     TextStyle savedStyle;
  798.     short oldCount, oldDelay;
  799.     char oldFade;
  800.     Rect oldBounds;
  801.     RgnHandle miniBlankRgn = NewRgn();
  802.     RGBColor miniSwarmBackground;
  803.     
  804.        PicHandle helpPict;                
  805.     Rect picRect, helpRect, miniRect;
  806.     Rect r;
  807.     short helpRectHeight;
  808.     Boolean runningAD30;
  809.     short onefourth, onethird, ninetenths;
  810.  
  811.  
  812.         // The miniswarm has a dark blue instead of a black background.
  813.     miniSwarmBackground = gBlue; 
  814.  
  815.         // Get the real screen.
  816.     GetPort(&helpGraf);
  817.  
  818.         // Get the real screen rectangle. miniRect is the version
  819.         // we’ll use in the rest of this function, helpRect is the
  820.         // ‘original’ version we’ll need to pass to the error handling 
  821.         // routine. Again, that last part is an ugly hack, and will be
  822.         // cleaned up in the next version.
  823.     helpRect = miniRect = helpGraf->portRect;
  824.     helpRectHeight = helpRect.bottom - helpRect.top;
  825.     
  826.         // AD 3.0 has this really weird bug: the portRect field of the helpPort
  827.         // doesn't correspond to what the portRect area *really* is. The following
  828.         // hack tries to determine if we are running under 3.0 , and if so it
  829.         // adjusts the portRect to what it really should be.
  830.     runningAD30 = (helpRectHeight > 270);
  831.     if (runningAD30)
  832.     {
  833.         helpRect.left++; 
  834.         helpRect.top++;
  835.     }
  836.     
  837.     RGBBackColor(&miniSwarmBackground);
  838.     EraseRect(&helpRect);
  839.  
  840.         // We want the layout of the about box elements to look right
  841.         // both for the large AD 3.0 box area and for the smaller AD 2.0
  842.         // area. That's why we use 'relative' height coordinates like
  843.         // 'onefourth' or 'onethird' instead of absolute coordinates.
  844.         
  845.     onefourth = helpRectHeight / 4;
  846.     onethird = helpRectHeight / 3;
  847.     ninetenths = (9*helpRectHeight) / 10;
  848.  
  849.         // Draw the PICT, and release the resource.
  850.         // Load the 'about' PICT resource.
  851.     if ((helpPict = GetPicture(2000)) == nil) 
  852.     {
  853.         RedAlert("\pCouldn’t load PICT resource for help picture!");
  854.         return ModuleError;
  855.     }
  856.     picRect = (**helpPict).picFrame;
  857.     CenterRectHorizontal(&picRect, onefourth / 2);
  858.     DrawPicture(helpPict, &picRect);
  859.     ReleaseResource((Handle) helpPict);
  860.         
  861.     GetPortTextStyle(&savedStyle);
  862.     RGBForeColor(&gGold);
  863.     LineFromTo(4, 5+onethird, helpRect.right-4, 5+onethird);
  864.     LineFromTo(4, 5+ninetenths, helpRect.right-4, 5+ninetenths);
  865.     TextFont(geneva); TextSize(9); TextFace(bold);
  866.     CenterString(34+onefourth/2, "\pA Screensaver Module");
  867.     CenterString(34+onefourth/2 + 12, "\pby Leo Breebaart");
  868.  
  869.     RGBForeColor(&gRed);
  870.     LineFromTo(4, 5+onethird+10, helpRect.right-4, 5+onethird+10);
  871.     LineFromTo(4, ninetenths-5, helpRect.right-4, ninetenths-5);
  872.     CenterString((ninetenths + helpRectHeight)/ 2 + 6, "\pKronto Software 1995");
  873.                  
  874.     SetRect(&miniRect, 0, 5+onethird+10+1, helpRect.right, ninetenths-5-1);
  875.  
  876.         // We now manually change the graphics environment
  877.         // from the entire help area to the subarea where we want the
  878.         // mini animation to take place (look at the about box in action if this
  879.         // is not clear to you). The current Port is moved and made smaller.
  880.         // The Toolbox calls take care of all the nasty details.
  881.     LocalToGlobal(&topLeft(miniRect));
  882.     LocalToGlobal(&botRight(miniRect));
  883.     MovePortTo(miniRect.left, miniRect.top);
  884.     PortSize(miniRect.right-miniRect.left, miniRect.bottom - miniRect.top);
  885.     
  886.         // Save some relevant parameters that will need to have restored later on
  887.         // for the 'real' After Dark animation.
  888.     oldCount  = params->monitors->monitorCount;
  889.     oldBounds = params->monitors->monitorList[0].bounds;
  890.     oldFade   = params->controlValues[2];
  891.     oldDelay  = params->controlValues[1];
  892.  
  893.         // Change the parameters temporarily. One monitor (not that the current main
  894.         // animation handles multiple monitors, mind you, but in a
  895.         // future version it will, while here we really want just one single screen.
  896.     params->monitors->monitorCount = 1;
  897.     params->monitors->monitorList[0].bounds = helpGraf->portRect;
  898.         // Don't use fading/color animation!
  899.     params->controlValues[2] = 0;
  900.     params->controlValues[1] = 100;
  901.     
  902.     RectRgn(miniBlankRgn, &helpGraf->portRect);
  903.     
  904.         // Initialize the miniSwarm struct. Note that we have a problem
  905.         // with error management here: I can use ErrorMsg all I want,
  906.         // but After Dark will do nothing with the return value of
  907.         // the DoAboutBox function, for some reason. So I just
  908.         // a generic Show-An-Alert error procedure here.
  909.     if (DoInitialize((Handle *) &miniSwarm, params) != noErr) 
  910.     {
  911.         RedAlert("\pInitialization of helpSwarm failed!");
  912.         return ModuleError;
  913.     }
  914.  
  915.         // Change some values to make them better suited for
  916.         // miniature animation. Notice that unlike 'params'
  917.         // before, we do not need to save the old values here,
  918.         // since miniSwarm is entirely local to this function.
  919.     (**miniSwarm).maxQueenVelocity = 7;
  920.     (**miniSwarm).maxBeeVelocity   = 6;
  921.     (**miniSwarm).nBees = 20;
  922.     (**miniSwarm).switchColor = 0;
  923.     (**miniSwarm).doFade = 0;
  924.     (**miniSwarm).backRGB = miniSwarmBackground;
  925.  
  926.         // Blank the miniSwarm region
  927.     if (DoBlank((Handle) miniSwarm, miniBlankRgn, params) != noErr)
  928.     {
  929.         RedAlert("\pDoBlank of helpSwarm failed!");
  930.         return ModuleError;
  931.     }
  932.     
  933.         // Wait for the user to release the mouse button, if necessary.
  934.     while (Button())
  935.         ;
  936.         
  937.         // Animate, until the user presses the mouse button.
  938.     while (!Button())
  939.     {
  940.         if (DoDrawFrame((Handle) miniSwarm, params) != noErr)
  941.         {
  942.             RedAlert("\pDoDrawFrame of helpSwarm failed!");
  943.             return ModuleError;
  944.         }
  945.             // uncrippled mini-animation is too fast!!
  946.         Delay(3, &dummy);
  947.     }
  948.         // Close it all up.
  949.     DoClose((Handle) miniSwarm, miniBlankRgn, params);
  950.  
  951.         // Restore old params values.
  952.     params->monitors->monitorCount = oldCount;
  953.     params->monitors->monitorList[0].bounds = oldBounds;
  954.     params->controlValues[2] = oldFade;
  955.     params->controlValues[1] = oldDelay;
  956.         
  957.         // Finally, display some text in the area where
  958.         // the animation used to be.
  959.  
  960.     if ((helpPict = GetPicture(2001)) == nil) 
  961.     {
  962.         RedAlert("\pCouldn’t load PICT resource for help picture!");
  963.         return ModuleError;
  964.     }
  965.     
  966.     RGBBackColor(&miniSwarmBackground);
  967.     SetRect(&r, 0, 0, miniRect.right-miniRect.left, miniRect.bottom - miniRect.top);
  968.     EraseRect(&r);
  969.     picRect = (**helpPict).picFrame;
  970.     CenterRect(&picRect, ((miniRect.right-miniRect.left) / 2), 
  971.                          ((miniRect.bottom-miniRect.top) / 2));
  972.     DrawPicture(helpPict, &picRect);
  973.     ReleaseResource((Handle) helpPict);
  974.     DisposeRgn(miniBlankRgn);
  975.     DisposeHandle((Handle) miniSwarm);
  976.  
  977.         // Wait for a final mouse click, and then exit.
  978.     while (Button())
  979.         ;
  980.      while (!Button())
  981.          ;
  982.      
  983.     SetPortTextStyle(&savedStyle);
  984.      FlushEvents(everyEvent, 0);    
  985.  
  986.     return noErr;
  987. }
  988.     
  989.  
  990.     // The DoClose function merely disposes of all those handles and 
  991.     // offscreen worlds. Nothing interesting here.
  992.  
  993. OSErr
  994. DoClose (Handle storage, RgnHandle blankRgn, GMParamBlockPtr params)
  995. {
  996.     TSwarmData **swarm = (TSwarmData **) storage;
  997.     
  998.     if ((**swarm).doFade && params->monitors->monitorList[0].curDepth == 8)
  999.     {    
  1000.         if ((**swarm).doFade)
  1001.             fade_screen(1,true);
  1002.         FillRgn(blankRgn, ¶ms->qdGlobalsCopy->qdBlack);
  1003.         if ((**swarm).doFade)
  1004.             fade_screen(1,false);
  1005.        }
  1006.        
  1007.     if (swarm)
  1008.     {
  1009.         SafeDisposHandle((**swarm).beeVelocityX);
  1010.         SafeDisposHandle((**swarm).beeVelocityY);
  1011.         SafeDisposHandle((**swarm).beeX[0]);
  1012.         SafeDisposHandle((**swarm).beeX[1]);
  1013.         SafeDisposHandle((**swarm).beeY[0]);
  1014.         SafeDisposHandle((**swarm).beeY[1]);
  1015.         
  1016.         DisposeGWorld((**swarm).gMyOffG);
  1017.         SafeDisposHandle(storage);
  1018.     }
  1019.  
  1020.     return noErr;
  1021. }
  1022.  
  1023.  
  1024.     // These functions are not used in this module, but we include them
  1025.     // here so the linker won't complain.
  1026. OSErr DoModuleSelected (GMParamBlockPtr params) { return noErr; }
  1027. OSErr DoButton (short message, GMParamBlockPtr params) { return noErr; }
  1028.  
  1029.  
  1030.     // This is THE END.
  1031.     // If you've learned anything from this code, or found errors in it, or
  1032.     // have questions about it, or whatever: feel free to drop me a note.
  1033.     // My e-mail address is: leo@cp.tn.tudelft.nl
  1034.